#pragma once
#include <map>
#include <algorithm>
#include <type_traits>
#include <nice2d/predef.h>
#include <nice2d/serializer.h>
#include <nice2d/metaobject.h>

class Component : public Serializer, public std::enable_shared_from_this<Component>
{
    DECLARE_OBJECT(Component)
public:
    Component(NodePointer object = nullptr);
    virtual ~Component();
public:
    void setOwner(NodePointer node);
    std::weak_ptr<Node> getOwner()const;
    virtual void update(float deltaTime);

    virtual PTree serialize() override;
    virtual void deserialize(const PTree &node) override;
private:
    std::weak_ptr<Node> owner;
};

class ComponentSet
{
public:
    virtual ~ComponentSet()
    {
    }
public:
    template<class T>
    std::shared_ptr<T> addComponent(const std::string &name)
    {
        auto component = getComponent<T>(name);
        if (component)
            return component;

        static_assert (std::is_base_of<Component, T>() && "component must be derived of Component");
        component = std::make_shared<T>();
        components.insert(std::make_pair<const std::string &, ComponentPointer>(name, component));
        return component;
    }

    void removeComponent(const std::string &name)
    {
        components.erase(name);
    }

    template<class T>
    std::shared_ptr<T> getComponent(const std::string &name)const
    {
        auto find = components.find(name);
        if (find == components.end())
            return nullptr;
        return std::dynamic_pointer_cast<T>(find->second);
    }

    void updateComponent(float deltaTime)
    {
        auto itr = components.begin();
        while (itr != components.end()) {
            (*itr).second->update(deltaTime);
            itr ++;
        }
    }

    void visitComponent(std::function<void(std::pair<std::string, ComponentPointer>)> pair)
    {
        std::for_each(components.begin(), components.end(), pair);
    }
private:
    std::map<std::string, ComponentPointer> components;
};

#define ADD_COMPONENT(object,component) object->addComponent<component>(#component)
#define GET_COMPONENT(object,component) object->getComponent<component>(#component)
